package com.stary.pay.unionpay.api;

import java.util.Map;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.stary.pay.unionpay.api.request.UnionpayEncryptCertUpdateQueryRequest;
import com.stary.pay.unionpay.api.request.UnionpayFileDownloadBillRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlinePagePayRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlinePagePreAuthPayRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlinePreAuthFinishRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlinePreAuthFinishUndoRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlinePreAuthUndoRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlineQueryRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlineRefundRequest;
import com.stary.pay.unionpay.api.request.UnionpayOnlineUndoRequest;
import com.stary.pay.unionpay.api.rules.UnionpayAccessType;
import com.stary.pay.unionpay.api.util.RequestParametersHolder;
import com.stary.pay.unionpay.api.util.StringUtils;
import com.stary.pay.unionpay.api.util.UnionpayCertHolder;
import com.stary.pay.unionpay.api.util.UnionpayHashMap;
import com.stary.pay.unionpay.api.util.UnionpayUtils;
import com.stary.pay.unionpay.api.util.WebUtils;

/**
 * <p>abstract unionpay client</p>
 * @author stary {@link stary1993@qq.com}
 * @since 2019-6-21
 */
public abstract class AbstractUnionpayClient implements UnionpayClient {
	
	/**
	 * 测试签名证书密码
	 */
	private static final String TEST_SIGNCERTPWD = "000000";
	
	/** 商户号 */
	private String					 merId;
	/** 接入类型 */
	private UnionpayAccessType		 accessType = UnionpayAccessType.MCH_DIRECT_CONNECT;
	/** 根证书  */
	private String 					 rootCertFile;
	/** 中级证书  */
	private String 					 middleCertFile;
	/** 签名证书 */
	private String					 signCertFile;
	/** 签名证书密码 */
	private String 					 signCertPwd = TEST_SIGNCERTPWD;
	/** 加密公钥证书 */
	private String 					 encryptCertFile;
	/** 安全密钥(SHA256和SM3计算时使用) */
	private String					 secureKey;
	/** 签名证书类型 */
	private String 					 signCertType = UnionpayConstants.SIGN_CERT_TYPE_PKCS12;
	/** 验证签名公钥证书目录 */
	private String 					 validateCertDir;
	/** 按照商户代码读取指定签名证书目录. */
	private String 					 signCertDir;
	/** 磁道加密证书 */
	private String 					 encryptTrackCertFile;
	/** 磁道加密公钥模数 */
	private String 					 encryptTrackKeyModulus;
	/** 磁道加密公钥指数 */
	private String 					 encryptTrackKeyExponent;
	/** 证书使用模式(单证书/多证书) */
	private String 					 singleMode;
	/** 是否验证验签证书CN */
	private boolean 				 isCheckCNName = false;
	/** 是否验证https证书，默认都不验  */
	private boolean 				 isCheckRemoteCert = false;
	/** 是否测试环境 */
	private boolean 				 isTest = true;
	/** 服务请求url  */
	private String					 serverUrl;
	/** 数据格式，默认json */
	private String 					 format = UnionpayConstants.FORMAT_JSON;
	/** 编码集，默认UTF-8 */
	private String					 charset = UnionpayConstants.CHARSET_UTF8;
	/** 签名方式，默认RSA */
	private String					 signMethod = UnionpayConstants.SIGNMETHOD_RSA;
	/** SDK版本号，默认5.1.0 */
	private String					 version = UnionpayConstants.VERSION_5_1_0;
	/** 连接超时毫秒 */
	private int						 connectTimeout = 3000;
	/** 读取超时毫秒 */
	private int						 readTimeout = 15000;

	/**
	 * 默认测试环境
	 * @param merId
	 * @param accessType
	 * @param rootCertFile
	 * @param middleCertFile
	 * @param signCertFile
	 * @param encryptCertFile
	 */
	public AbstractUnionpayClient(String merId, UnionpayAccessType accessType, String rootCertFile,
			String middleCertFile, String signCertFile, String encryptCertFile) {
		super();
		this.merId = merId;
		if (accessType == null) {
			accessType = UnionpayAccessType.MCH_DIRECT_CONNECT;
		}
		this.accessType = accessType;
		this.rootCertFile = rootCertFile;
		this.middleCertFile = middleCertFile;
		this.signCertFile = signCertFile;
		this.encryptCertFile = encryptCertFile;
	}	
	/**
	 * 默认正式环境
	 * @param merId
	 * @param accessType
	 * @param rootCertFile
	 * @param middleCertFile
	 * @param signCertFile
	 * @param signCertPwd
	 * @param encryptCertFile
	 * @param secureKey
	 */
	public AbstractUnionpayClient(String merId, UnionpayAccessType accessType, String rootCertFile,
			String middleCertFile, String signCertFile, String signCertPwd,
			String encryptCertFile, String secureKey) {
		super();
		this.merId = merId;
		if (accessType == null) {
			accessType = UnionpayAccessType.MCH_DIRECT_CONNECT;
		}
		this.accessType = accessType;
		this.rootCertFile = rootCertFile;
		this.middleCertFile = middleCertFile;
		this.signCertFile = signCertFile;
		this.signCertPwd = signCertPwd;
		this.encryptCertFile = encryptCertFile;
		this.secureKey = secureKey;
		this.setTest(false);
	}

	public AbstractUnionpayClient(String merId, UnionpayAccessType accessType, String rootCertFile,
			String middleCertFile, String signCertFile, String signCertPwd,
			String encryptCertFile, String secureKey, boolean isTest) {
		super();
		this.merId = merId;
		if (accessType == null) {
			accessType = UnionpayAccessType.MCH_DIRECT_CONNECT;
		}
		this.accessType = accessType;
		this.rootCertFile = rootCertFile;
		this.middleCertFile = middleCertFile;
		this.signCertFile = signCertFile;
		this.signCertPwd = signCertPwd;
		this.encryptCertFile = encryptCertFile;
		this.secureKey = secureKey;
		this.isTest = isTest;
		if (isTest) {
			this.signCertPwd = TEST_SIGNCERTPWD;
		}
	}

	/**
	 * 初始化所有证书.
	 */
	public void initCerts() {
		try {
			// 向系统添加BC provider
			UnionpayCertHolder.addProvider();
			// 初始化签名私钥证书
			UnionpayCertHolder.initSignCert(this.signMethod, this.signCertFile, this.signCertPwd, this.signCertType);
			// 初始化验签证书的根证书
			UnionpayCertHolder.initRootCert(this.rootCertFile);
			// 初始化验签证书的中级证书
			UnionpayCertHolder.initMiddleCert(this.middleCertFile);
			// 初始化加密公钥
			UnionpayCertHolder.initEncryptCert(this.encryptCertFile);
			// 构建磁道加密公钥
			UnionpayCertHolder.initTrackKey(this.encryptTrackKeyModulus, this.encryptTrackKeyExponent);
			//初始化所有的验签证书
			UnionpayCertHolder.initValidateCertFromDir(this.signMethod, this.validateCertDir);
			UnionpayUtils.getLogger().info("init unionpay certs succeed");
		} catch (Exception e) {
			UnionpayUtils.getLogger().error("init unionpay certs failed", e);
		}
	}
	public <T extends UnionpayResponse> T execute(UnionpayRequest<T> request) throws UnionpayApiException {
		T tRsp = null;
		UnionpayHashMap requestParams = 
				getRequestHolderWithSign(request, this.secureKey, this.signCertPwd).getApplicationParams();
		String requestUrl = getRequestUrl(request);	
		this.serverUrl = requestUrl;
		UnionpayUtils.getLogger().debug("request params:" + requestParams.toString());
		String respStr = WebUtils.requestPost(requestUrl, requestParams, this.connectTimeout, this.readTimeout, this.charset);
		UnionpayUtils.getLogger().debug("response result:" + respStr);		
		if (!StringUtils.isEmpty(respStr)) {
			UnionpayHashMap respHashMap = UnionpayUtils.convertResultStringToMap(respStr);
			if (request.isCheckSign()) {
				if (!UnionpayUtils.checkSign(respHashMap, this.secureKey, this.validateCertDir, 
						this.rootCertFile, this.middleCertFile, this.isCheckCNName, this.charset)) {
					throw new UnionpayApiException("checkSign fail");
				}
			}
			try {
				tRsp = UnionpayUtils.mapToObject(respHashMap, request.getResponseClass());				
			} catch (Exception e) {
				throw new UnionpayApiException("an unexpected error", e);
			}
		}
		tRsp.setParams(requestParams);
		return tRsp;
	}

	public <T extends UnionpayResponse> T pageExecute(UnionpayRequest<T> request) throws UnionpayApiException {
		T tRsp = null;
		UnionpayHashMap requestParams = 
				getRequestHolderWithSign(request, this.secureKey, this.signCertPwd).getApplicationParams();
		String requestUrl = getRequestUrl(request);
		this.serverUrl = requestUrl;
		String body = UnionpayUtils.createAutoFormHtml(requestUrl, requestParams, this.charset);
		try {
			tRsp = request.getResponseClass().newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			throw new UnionpayApiException("an unexpected error", e);
		}
		tRsp.setRespCode("00");
		tRsp.setRespMsg(UnionpayConstants.SUCCESS);
		tRsp.setBody(body);
		tRsp.setParams(requestParams);
		tRsp.setVersion(this.version);
		tRsp.setEncoding(this.charset);
		return tRsp;
	}
	/**
	 * 获取并匹配请求url
	 * @param request
	 * @return 请求url
	 */
	private String getRequestUrl(UnionpayRequest<?> request) {
		String requestUrl = "";
		String requestDomain = UnionpayConstants.DOMAIN_GATEWAY;
		if (this.isTest) {
			requestDomain = UnionpayConstants.DOMAIN_GATEWAY_TEST;
		}
		if (request instanceof UnionpayOnlineQueryRequest) {
			requestUrl = requestDomain + UnionpayConstants.QUERYTRANS_URL_SUFFIX;
		} else if (request instanceof UnionpayOnlinePagePayRequest
				|| request instanceof UnionpayOnlinePagePreAuthPayRequest) {
			requestUrl = requestDomain + UnionpayConstants.FRONTTRANS_URL_SUFFIX;
		} else if (request instanceof UnionpayOnlineUndoRequest 
				|| request instanceof UnionpayOnlineRefundRequest
				|| request instanceof UnionpayOnlinePreAuthFinishRequest
				|| request instanceof UnionpayOnlinePreAuthFinishUndoRequest
				|| request instanceof UnionpayOnlinePreAuthUndoRequest
				|| request instanceof UnionpayEncryptCertUpdateQueryRequest) {
			requestUrl = requestDomain + UnionpayConstants.BACKTRANS_URL_SUFFIX;
		} else if (request instanceof UnionpayFileDownloadBillRequest) {
			if (this.isTest) {
				requestUrl = UnionpayConstants.DOMAIN_FILEDOWLOAD_TEST;				
			} else {
				requestUrl = UnionpayConstants.DOMAIN_FILEDOWLOAD;	
			}
		}
		return requestUrl;
	}
	/**
	 * 获取带签名的请求参数体
	 * @param request
	 * @param secureKey 安全秘钥
	 * @param signCertPwd 签名证书密码
	 * @return 带签名的请求参数体
	 * @throws UnionpayApiException
	 */
	private <T extends UnionpayResponse> RequestParametersHolder getRequestHolderWithSign(UnionpayRequest<?> request, String secureKey, String signCertPwd) throws UnionpayApiException {
		RequestParametersHolder requestHolder = new RequestParametersHolder();
		UnionpayHashMap appParams = new UnionpayHashMap(request.getTextParams());
		JSONObject bizContent = new JSONObject();
		try {
			if (request.getClass().getMethod("getBizContent") != null
					&& !StringUtils.isEmpty(appParams.get(UnionpayConstants.BIZ_CONTENT))) {
				try {
					bizContent = JSONObject.parseObject(appParams.get(UnionpayConstants.BIZ_CONTENT));
				} catch (JSONException e) {
					throw new UnionpayApiException("biz content data format error", e);
				}				
			}
		} catch (NoSuchMethodException | SecurityException e) {	
			throw new UnionpayApiException("an unexpected error", e);
		}
		bizContent.put(UnionpayConstants.VERSION, this.version);
		bizContent.put(UnionpayConstants.MERID, this.merId);
		bizContent.put(UnionpayConstants.ENCODING, this.charset);
		bizContent.put(UnionpayConstants.SIGNMETHOD, this.signMethod);
		bizContent.put(UnionpayConstants.BIZTYPE, request.getBizType());
		bizContent.put(UnionpayConstants.ACCESSTYPE, this.getAccessType().getType());
		if (!bizContent.containsKey(UnionpayConstants.ACCESSTYPE)) {			
			bizContent.put(UnionpayConstants.ACCESSTYPE, UnionpayAccessType.MCH_DIRECT_CONNECT.getType());
		}
		if (!bizContent.containsKey(UnionpayConstants.TXNTIME)) {
			bizContent.put(UnionpayConstants.TXNTIME, UnionpayUtils.getCurrentTime());			
		}
		if (!StringUtils.isEmpty(request.getTxnType())) {
			bizContent.put(UnionpayConstants.TXNTYPE, request.getTxnType());
			if (!UnionpayConstants.TXNTYPE_ENCRYPT_CERT_UPDATE_QUERY.equals(request.getTxnType())) {
				if (!bizContent.containsKey(UnionpayConstants.CURRENCYCODE)) {
					bizContent.put(UnionpayConstants.CURRENCYCODE, UnionpayConstants.CURRENCYCODE_RMB);
				}				
			} else {
				bizContent.put(UnionpayConstants.CERTTYPE, UnionpayConstants.CERT_TYPE_01);
			}
		}		
		if (!StringUtils.isEmpty(request.getTxnSubType())) {
			bizContent.put(UnionpayConstants.TXNSUBTYPE, request.getTxnSubType());
		}
		if (!StringUtils.isEmpty(request.getChannelType())) {
			bizContent.put(UnionpayConstants.CHANNELTYPE, request.getChannelType());
		}
		if (!StringUtils.isEmpty(request.getFrontUrl())) {
			bizContent.put(UnionpayConstants.FRONTURL, request.getFrontUrl());
		}
		if (!StringUtils.isEmpty(request.getBackUrl())) {
			bizContent.put(UnionpayConstants.BACKURL, request.getBackUrl());
		}
		@SuppressWarnings("unchecked")
		Map<String, String> map = JSONObject.toJavaObject(bizContent,Map.class);
		appParams = new UnionpayHashMap(map);
		appParams = UnionpayUtils.sign(appParams, secureKey, signCertPwd, this.charset);
		requestHolder.setApplicationParams(appParams);
		return requestHolder;
	}

	public String getMerId() {
		return merId;
	}

	public void setMerId(String merId) {
		this.merId = merId;
	}

	public UnionpayAccessType getAccessType() {
		return accessType;
	}

	public void setAccessType(UnionpayAccessType accessType) {
		if (accessType == null) {
			accessType = UnionpayAccessType.MCH_DIRECT_CONNECT;
		}
		this.accessType = accessType;
	}

	public String getRootCertFile() {
		return rootCertFile;
	}

	public void setRootCertFile(String rootCertFile) {
		this.rootCertFile = rootCertFile;
	}

	public String getMiddleCertFile() {
		return middleCertFile;
	}

	public void setMiddleCertFile(String middleCertFile) {
		this.middleCertFile = middleCertFile;
	}

	public String getSignCertFile() {
		return signCertFile;
	}

	public void setSignCertFile(String signCertFile) {
		this.signCertFile = signCertFile;
	}

	public String getSignCertPwd() {
		return signCertPwd;
	}

	public void setSignCertPwd(String signCertPwd) {
		this.signCertPwd = signCertPwd;
	}

	public String getEncryptCertFile() {
		return encryptCertFile;
	}

	public void setEncryptCertFile(String encryptCertFile) {
		this.encryptCertFile = encryptCertFile;
	}

	public String getSecureKey() {
		return secureKey;
	}

	public void setSecureKey(String secureKey) {
		this.secureKey = secureKey;
	}

	public String getSignCertType() {
		return signCertType;
	}

	public void setSignCertType(String signCertType) {
		this.signCertType = signCertType;
	}

	public String getValidateCertDir() {
		return validateCertDir;
	}

	public void setValidateCertDir(String validateCertDir) {
		this.validateCertDir = validateCertDir;
	}

	public String getSignCertDir() {
		return signCertDir;
	}

	public void setSignCertDir(String signCertDir) {
		this.signCertDir = signCertDir;
	}

	public String getEncryptTrackCertFile() {
		return encryptTrackCertFile;
	}

	public void setEncryptTrackCertFile(String encryptTrackCertFile) {
		this.encryptTrackCertFile = encryptTrackCertFile;
	}

	public String getEncryptTrackKeyModulus() {
		return encryptTrackKeyModulus;
	}

	public void setEncryptTrackKeyModulus(String encryptTrackKeyModulus) {
		this.encryptTrackKeyModulus = encryptTrackKeyModulus;
	}

	public String getEncryptTrackKeyExponent() {
		return encryptTrackKeyExponent;
	}

	public void setEncryptTrackKeyExponent(String encryptTrackKeyExponent) {
		this.encryptTrackKeyExponent = encryptTrackKeyExponent;
	}

	public String getSingleMode() {
		return singleMode;
	}

	public void setSingleMode(String singleMode) {
		this.singleMode = singleMode;
	}

	public boolean isCheckCNName() {
		return isCheckCNName;
	}

	public void setCheckCNName(boolean isCheckCNName) {
		this.isCheckCNName = isCheckCNName;
	}

	public boolean isCheckRemoteCert() {
		return isCheckRemoteCert;
	}

	public void setCheckRemoteCert(boolean isCheckRemoteCert) {
		this.isCheckRemoteCert = isCheckRemoteCert;
	}

	public boolean isTest() {
		return isTest;
	}

	public void setTest(boolean isTest) {
		if (isTest) {
			this.signCertPwd = TEST_SIGNCERTPWD;
		}
		this.isTest = isTest;
		this.isCheckCNName = !isTest;
		this.isCheckRemoteCert = !isTest;
	}

	public String getServerUrl() {
		return serverUrl;
	}

	public void setServerUrl(String serverUrl) {
		this.serverUrl = serverUrl;
	}

	public String getFormat() {
		return format;
	}

	public void setFormat(String format) {
		this.format = format;
	}
	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

	public String getSignMethod() {
		return signMethod;
	}

	public void setSignMethod(String signMethod) {
		this.signMethod = signMethod;
	}

	public String getVersion() {
		return version;
	}

	public void setVersion(String version) {
		this.version = version;
	}

	public int getConnectTimeout() {
		return connectTimeout;
	}

	public void setConnectTimeout(int connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public int getReadTimeout() {
		return readTimeout;
	}

	public void setReadTimeout(int readTimeout) {
		this.readTimeout = readTimeout;
	}

}
